<template>
  <!-- simple DataColumn for Visual View  -->
  <div class="column-container">
    <div class="view-content">
      <div class="top-icons" @click="activeColumn">
        <div class="column-type-icon icon-container">
          <column-data-type-menu>
            <a-icon-font :type="columnTypeIcon[column.type]" />
          </column-data-type-menu>
        </div>
        <div>
          <span
            class="ant-checkbox"
            :class="{
              'ant-checkbox-disabled': column.isNumber && !column.isDate,
              'ant-checkbox-checked': fieldList.find(
                (item) => item.label === column.label
              ),
            }"
          >
            <span class="ant-checkbox-inner"></span>
          </span>
        </div>
      </div>
      <div class="name-and-btns" @click="activeColumn">
        <div class="column-name">
          <span>{{ column.label }}</span>
        </div>
        <div v-if="chartConfig" class="unique-value-count">
          {{ columnStat.distinctTotal }}
        </div>
        <div class="btns icon-container">
          <a-icon-font class="icon" type="iconsousuo" @click="setSearchMode" />
          <column-sort-menu @click="sortColumn">
            <a-icon-font class="icon" type="iconpaixu" />
          </column-sort-menu>
          <a-dropdown v-show="isShowToggleStatMode" :trigger="['click']">
            <a-icon-font class="icon" type="icongengduocaozuo" />
            <a-menu slot="overlay" @click="({ key }) => toggleStatMode(key)">
              <a-menu-item-group title="查看状态">
                <a-menu-item key="detail"
                  >{{
                    statMode === 'detail' ? '√ ' : '  '
                  }}详细信息</a-menu-item
                >
                <a-menu-item key="binned"
                  >{{ statMode === 'binned' ? '√ ' : '  ' }}摘要</a-menu-item
                >
              </a-menu-item-group>
            </a-menu>
          </a-dropdown>
        </div>
      </div>
      <div class="divider"></div>
      <div class="stat-chart" :style="{ height: `${chartHeight}px` }">
        <div v-show="isSearchActive" class="search-box">
          <a-input
            ref="searchInput"
            v-model="searchValue"
            size="small"
            class="search-input"
            placeholder="按回车键发起搜索"
            @pressEnter="searchOptions"
          />
        </div>
        <a-input
          v-show="chartOptionsEdit"
          v-model="chartOptionsInputValue"
          sime="small"
          class="chart-options-input"
          :style="chartOptionsInputStyle"
        />
        <column-statistics
          v-if="chartConfig"
          ref="columnStatistics"
          :width="160"
          :height="chartHeight"
          v-bind="chartConfig"
          @bar-click="onBarClick"
          @bar-scroll="onBarScroll"
          @range-bar-click="onOverviewMove"
        />
        <div v-else class="mask-bar-container">
          <div
            v-for="(barLength, i) in maskBarLength"
            :key="i"
            :style="{ width: `${barLength}px` }"
            class="mask-bar"
          ></div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Vue, Component, Prop, Watch } from 'vue-property-decorator'
import Chart from '@/components/chart/chart.vue'
import {
  ColumnStat,
  EventMessage,
} from '@/components/studio/data/node-edit-panel/interface'
import { columnTypeIcon } from '@/components/studio/data/node-edit-panel/column-type-semantic-icon'
import { apiGetColumnStat } from '@/api/data-clean'
import ColumnStatistics from '@/components/studio/data/node-edit-panel/ColumnStatistics.vue'
import ColumnSortMenu from '@/components/studio/data/node-edit-panel/ColumnSortMenu.vue'
import ColumnDataTypeMenu from '@/components/studio/data/node-edit-panel/ColumnDataTypeMenu.vue'
import { flatten, isEqual, throttle } from 'lodash'

interface ChartDataItem {
  label: string
  value: [number, number]
  low?: string | number
  up?: string | number
}

interface ChartUpdateOptions {
  scroll?: 'up' | 'down'
  updateOverview?: boolean
  pageNumber?: number
}

interface IKeyValueStore {
  [key: string]: any
}

@Component({
  components: {
    Chart,
    ColumnStatistics,
    ColumnSortMenu,
    ColumnDataTypeMenu,
  },
})
export default class VisualDataColumn extends Vue {
  @Prop() private column!: any
  @Prop() tableName!: 'string'
  @Prop() fieldList!: any[]
  @Prop({ default: () => [] }) columnFilters!: any[]

  private chartConfig: Object | null = null
  private columnStat: ColumnStat | null = null
  private chartHeight: number = 180
  private columnTypeIcon = columnTypeIcon // 统一数据类型 icon
  private maskBarLength: number[] = [112, 81, 81, 81, 38, 57, 75, 112]
  private visibleBarNumber = 8 // cjh TODO
  private overviewBarNumber = 60 // cjh TODO
  private mode: 'view' | 'dialog' | 'panel' = 'view' // view：详情态，dialog：dialog模式，panel：面板模式
  private configPanelType = 'filter-selected-value'
  private configPanelTitle = ''
  private filterParams: any = null // 清洗操作面板抛出的清洗参数
  private chartSelectedDataArray: any[] = [] // bar点击选中的选项
  private isSorting = false
  private isSearchActive: boolean = false
  private isEditingColumnName: boolean = false
  private userSetStatMode: string = ''
  private searchValue = '' // 搜索输入值
  private searchFilter: any[] = []
  private chartOptionsInputValue = '' // chart中unique值修改
  private chartOptionsEdit = false
  private chartOptionsInputStyle: any = {}

  private requestParams = {
    curPage: 1,
    pageSize: 50,
    sortType: 1,
    sortVal: 'ASC',
  }
  private oldBarScrollPayload = null
  private oldOverviewSelectedIndex = null

  private get statMode(): string {
    if (!this.column || !this.column.type) {
      return ''
    }

    let mode = 'binned'
    if (this.isSorting) {
      mode = 'detail'
    } else if (this.userSetStatMode) {
      mode = this.userSetStatMode
    } else if (
      ['varchar', 'json', 'array', 'date'].includes(this.column.type)
    ) {
      // 2021.08.21 时间查看 详情 增加 date
      mode = 'detail'
    }

    return mode
  }

  private get isShowToggleStatMode() {
    return ['int', 'decimal', 'date'].includes(this.column.type)
  }

  mounted() {
    this.init()
  }

  private async init() {
    this.columnStat = await this.getNewColumnStat()
    this.chartConfig = this.getChartConfig(this.columnStat)
  }

  private get componentParams() {
    return {
      ...this.requestParams,
      // taskId: this.selectNode.id,
      table: this.tableName,
      name: this.column.label,
      type: this.column.type,
      mode: 1, // 详情
    }
  }

  /**
   * 查询字段统计信息的基本参数
   */
  private get columnStatParams() {
    const data: any = {
      ...this.requestParams,
      table: this.tableName,
      name: this.column.label,
      type: this.column.type_,
      mode: this.statMode === 'detail' ? 1 : 2,
      filter: this.columnFilters,
      // taskId: this.selectNode.id,
    }
    if (this.isSearchActive) {
      data.searchFilter = this.searchFilter
    }
    return data
  }

  /**
   * 查询字段统计信息的额外参数，用于后端减少查询开销
   * 上次响应中的几个字段，仅依赖taskId，table，column
   */
  private get stableColumnStatParams() {
    if (!this.columnStat) {
      return null
    }
    const { total, distinctTotal, topColumn, max, min } = this.columnStat
    return { total, distinctTotal, topColumn, max, min }
  }

  @Watch('columnFilters')
  private async onColumnFiltersChange(newValue: any, oldValue: any) {
    if (isEqual(newValue, oldValue)) {
      return
    }
    if (this.statMode === 'binned') {
      this.columnStat = await this.updateColumnStat()
      this.chartConfig = this.getChartConfig(this.columnStat)
    } else {
      // const chart = (<Chart>this.$refs.chart).compInstance
      const chart = this.$refs.columnStatistics as Vue
      // TODO
      const startBarIndex: number | undefined = chart.$props.value[0]?.index
      if (startBarIndex !== undefined) {
        const value = await this.getChartDataByIndex(startBarIndex)
        const overviewData = this.rangeCountToChartOverview(
          this.columnStat?.rangeCount ?? []
        )
        this.updateChartData(value)
        this.updateChartData(overviewData, { updateOverview: true })
      }
    }
  }

  private updateColumnStat(): Promise<ColumnStat | null> {
    const parameters: any = this.columnStatParams
    if (this.stableColumnStatParams) {
      parameters.data = this.stableColumnStatParams
    }
    return this.getColumnStat(parameters)
  }

  private getNewColumnStat(): Promise<ColumnStat | null> {
    return this.getColumnStat(this.columnStatParams)
  }

  private async getColumnStat(data: any): Promise<ColumnStat | null> {
    if (!this.column) {
      return null
    }
    const response = await apiGetColumnStat({ data })
    if (response.status === 200 && response.data.success) {
      return response.data.result
    }
    return null
  }

  private getChartConfig(columnStat: ColumnStat | null) {
    if (!columnStat) {
      return null
    }

    let mode: string
    let maxValue: number
    let value: { label: any; value: number[] }[]
    let overviewOptions: number[][]
    let barRatio: number = 1

    if (this.statMode === 'detail') {
      mode = 'discrete'
      maxValue = columnStat.topColumn.count
      value = this.pageCountToChartValue(columnStat.pageCount) || []
      overviewOptions = this.rangeCountToChartOverview(columnStat.rangeCount)
      const rangeCountElement = columnStat.rangeCount[0]! || {}
      barRatio = rangeCountElement.up - rangeCountElement?.low
    } else {
      mode = 'continuous'
      maxValue = Math.max(...columnStat.rangeCount.map((d) => d.count))
      value = columnStat.rangeCount.map((d, i) => ({
        label: d.low === null ? 'null' : `${d.low} - ${d.up}`,
        index: i,
        yAxisValue: d.low,
        value: [d.count, d.highLight || 0],
        low: d.low,
        up: d.up,
      }))
      overviewOptions = []
    }

    return {
      maxValue,
      totalValue: columnStat.distinctTotal,
      mode,
      radio: barRatio,
      value,
      totalCount: columnStat.total,
      overviewOptions,
      barHeight: mode === 'continuous' ? 10 : 20,
      barGap: mode === 'continuous' ? 0 : 1,
      tooltipIsShow: true,
      barColor: '#E9E9E9',
      subBarColor: '#B6BAF1',
      overviewFocusBarStrokeColor: '#D8D8D8',
    }
  }

  private async refreshColumnStat() {
    this.chartConfig = null
    this.columnStat = await this.updateColumnStat()
    this.$nextTick(() => {
      this.chartConfig = this.getChartConfig(this.columnStat)
    })
  }

  private onBarClick(data: any) {
    const filters: any[] = []
    this.chartSelectedDataArray = data.selectedDataArray

    // TODO: bug。filter结构好像需要再多一层[[[and]or]and]
    data.selectedDataArray.forEach((d: ChartDataItem) => {
      if (this.statMode === 'binned') {
        if (d.low === null) {
          filters.push([
            {
              col: this.column.label,
              values: ['#NULL'],
              filterType: '=',
            },
          ])
        } else {
          filters.push([
            {
              col: this.column.label,
              values: [`${d.low}`],
              filterType: '>=',
            },
          ])
          filters.push([
            {
              col: this.column.label,
              values: [`${d.up}`],
              filterType: '<',
            },
          ])
        }
      } else {
        filters.push([
          {
            col: this.column.label,
            values: [d.label === null ? '#NULL' : `${d.label}`],
            filterType: '=',
          },
        ])
      }
    })
    this.$emit('column-filter', filters)
    //  DataStore.setColumnFilters(filters)
  }

  /**
   * throttled _onOverviewMove
   */
  private onOverviewMove = throttle((data) => this._onOverviewMove(data), 300)

  private async _onOverviewMove({ selectedRangeIndex }: any = {}) {
    if (this.oldOverviewSelectedIndex === selectedRangeIndex) {
      return
    }
    this.oldOverviewSelectedIndex = selectedRangeIndex

    // if (selectedOverviewIndex.length === 0) return // TODO: error param
    const distinctTotal = this.columnStat?.distinctTotal
    const rangeCountElement = this.columnStat?.rangeCount[0]!
    const barRatio = rangeCountElement.up - rangeCountElement?.low
    const startBarIndex = Math.min(
      Math.round(selectedRangeIndex) * barRatio,
      distinctTotal! - this.visibleBarNumber
    )
    const value = await this.getChartDataByIndex(startBarIndex)
    if (!value) {
      return
    }
    ;(this.chartConfig as any).value = value
    this.updateChartData(value)
    ;(this.$refs.columnStatistics as any).scrollToTop(0)
  }

  private async onBarScroll(payload: any) {
    if (isEqual(this.oldBarScrollPayload, payload)) {
      return
    }
    this.oldBarScrollPayload = payload

    const { deltaY, currentDataStartIndex, currentDataEndIndex } = payload
    const { pageSize } = this.requestParams
    const distinctTotal = this.columnStat?.distinctTotal
    const pageNumber = Math.ceil(distinctTotal! / pageSize)
    const nextPage =
      currentDataEndIndex + 1 < distinctTotal!
        ? Math.ceil((currentDataEndIndex + 2) / pageSize)
        : null
    const previousPage = Math.ceil(currentDataStartIndex / pageSize)

    let newPage
    if (deltaY > 0 && nextPage && nextPage <= pageNumber) {
      newPage = nextPage
    } else if (deltaY < 0 && previousPage >= 1) {
      newPage = previousPage
    } else {
      return
    }

    const value = await this.getDetailDataPage(newPage)
    if (!value) {
      return
    }

    const valueSlice = value.slice(
      deltaY > 0 ? currentDataEndIndex + 1 - pageSize * (newPage - 1) : 0,
      deltaY > 0
        ? value.length
        : currentDataStartIndex - pageSize * (newPage - 1)
    )

    this.updateChartData(valueSlice, { scroll: deltaY > 0 ? 'down' : 'up' })
  }

  private async getChartDataByIndex(startBarIndex: number) {
    const { pageSize } = this.requestParams
    const startPage = Math.ceil((startBarIndex + 1) / pageSize)

    const endPage = Math.min(
      Math.ceil((startBarIndex + this.visibleBarNumber) / pageSize),
      this.columnStat?.pageCount?.totalPages!
    )
    const requests = new Array(endPage - startPage + 1)
      .fill(null)
      .map((_, i) => {
        this.requestParams.curPage = startPage + i
        return this.updateColumnStat()
      })
    const responses = await Promise.all(requests)
    this.columnStat = responses[responses.length - 1]

    return flatten(
      responses.map((response) =>
        this.pageCountToChartValue(response!.pageCount)
      )
    ).slice(startBarIndex - pageSize * (startPage - 1))
  }

  private async getDetailDataPage(page: number): Promise<any[] | null> {
    let pageCount: ColumnStat['pageCount'] | null

    if (page === this.columnStat?.pageCount?.curPage) {
      pageCount = this.columnStat.pageCount // cache
    } else if (page === this.requestParams.curPage) {
      pageCount = null // 请求已发未回
    } else {
      this.requestParams.curPage = page
      const columnStat = await this.updateColumnStat()
      if (page !== columnStat?.pageCount?.curPage) {
        pageCount = null // 已有更加新的请求返回
      } else {
        pageCount = columnStat?.pageCount
        this.columnStat = columnStat
      }
    }

    if (!pageCount) {
      return null
    }
    return this.pageCountToChartValue(pageCount)
  }

  private pageCountToChartValue(pageCount: ColumnStat['pageCount']) {
    const { data, curPage, pageSize } = pageCount || {}
    if (!data) {
      return null
    }
    return data.map((d, i) => ({
      label: d.column,
      index: pageSize! * (curPage! - 1) + i,
      yAxisValue: d.column,
      value: [d.count, d.highLight || 0],
    }))
  }

  private rangeCountToChartOverview(rangeCount: ColumnStat['rangeCount']) {
    return rangeCount.map((d) => [d.count, d.highLight || 0])
  }

  private updateChartData(value: any[], options?: ChartUpdateOptions) {
    // const chart = (<Chart>this.$refs.chart)?.compInstance
    const chart = this.$refs.columnStatistics
    if (!chart) {
      return
    }

    let message: EventMessage
    if (options?.scroll) {
      message = new EventMessage(
        { detailData: value, dataPosition: options.scroll },
        'updatePartData',
        chart
      )
    } else if (options?.updateOverview) {
      message = new EventMessage(
        { overviewData: value },
        'updateOverviewData',
        chart
      )
    } else {
      message = new EventMessage({ data: value }, 'updateAllData', chart)
    }

    ;(chart as any).reloadData(message)
    // chart.$emit('part-render-event', message)
  }

  // private get selectedColumns() {
  //   return DataStore.selectedColumns
  // }

  private async sortColumn({ by, order }: any) {
    this.isSorting = true
    this.requestParams = {
      ...this.requestParams,
      sortType: by === 'count' ? 2 : 1,
      sortVal: order,
      curPage: 1,
    }

    this.refreshColumnStat()
  }

  /**
   * 设置搜索框是否启用
   */
  private setSearchMode() {
    this.isSearchActive = !this.isSearchActive
    this.oldBarScrollPayload = null
    if (!this.isSearchActive && this.searchValue.trim()) {
      // 关闭搜索并且之前搜索值不为空时，需要重置
      this.searchValue = ''
      this.searchOptions()
      this.$nextTick(() => {
        this.searchFilter = []
      })
    }
    this.chartHeight = this.isSearchActive ? 150 : 180
  }

  /**
   * 模糊搜索选项
   */
  private async searchOptions() {
    const searchValue = this.searchValue.trim()
    this.searchFilter = [
      [
        {
          col: this.column.label,
          filterType: '~',
          values: [searchValue],
        },
      ],
    ]
    this.columnStat = await this.getColumnStat({
      ...this.columnStatParams,
      searchFilter: this.searchFilter,
      mode: 1,
      curPage: 1,
    })
    this.userSetStatMode = 'detail'
    this.chartConfig = this.getChartConfig(this.columnStat)
  }

  private async toggleStatMode(mode: string) {
    if (this.statMode !== mode) {
      this.userSetStatMode = mode
      this.refreshColumnStat()
    }
  }

  /**
   * 勾选/取消字段
   */
  private toggleField(column: any) {
    this.$emit('togglefield', column)
  }

  private activeColumn() {
    const column = this.column as any
    if (!(column.isNumber && !column.isDate)) {
      this.toggleField(column)
    }
    // if(!(this.column.isNumber && !this.column.isDate )){
    //   this.$emit('activecolumn', this.column)
    // }
  }
}
</script>

<style lang="less" scoped>
.column-container {
  background: #fff;
  border: 1px solid transparent;
  border-radius: 4px;
  box-shadow: 0 0 4px 0 rgba(88, 98, 230, 0.2);
  flex: 0 0 180px;
  height: 240px;
  margin: 0 3px;
  overflow: hidden;
  position: relative;

  &.selected {
    background: #fff;
    box-shadow: 0 0 0 1px #c5c9ff, 0 0 0 3px rgba(171, 176, 255, 0.1),
      0 0 4px 0 rgba(88, 98, 230, 0.2);
  }

  &.panel {
    display: flex;
    flex: 0 0 400px;
  }

  .view-content {
    display: inline-block;
    width: 180px;
  }

  .config-panel {
    border-left: 1px solid #e9e9e9;
    display: inline-block;
    height: 100%;
    margin-left: 6px;
    padding: 24px 8px 8px;
    width: 220px;
  }

  .edit-header {
    align-items: center;
    background-color: #cdd0f7;
    display: flex;
    height: 24px;
    justify-content: space-between;
    left: 0;
    padding: 0 8px;
    position: absolute;
    top: 0;
    width: 400px;

    .btn-done {
      cursor: pointer;
    }
  }

  .top-icons {
    align-items: center;
    cursor: pointer;
    display: flex;
    height: 24px;
    justify-content: space-between;
    padding: 6px 8px;

    .column-type-icon {
      float: left;
      height: 12px;
      width: 12px;
    }

    .applied-action-icon {
      border: 1px solid #5d637e;
      float: right;
      height: 12px;
      margin-left: 4px;
      width: 12px;
    }
  }

  .name-and-btns {
    height: 20px;
    padding: 0 8px;

    .column-name {
      color: #373b52;
      float: left;
      font-size: 14px;
      height: 100%;
      max-width: 100px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .unique-value-count {
      color: #222432;
      float: left;
      font-size: 12px;
      height: 100%;
      letter-spacing: 1.12px;
      margin-left: 10px;
      max-width: 54px;
      opacity: 0.5;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .btns {
      color: #5d637e;
      display: none;
      float: right;
      margin-top: 4px;
      width: 60px;

      .icon {
        margin-left: 8px;
      }
    }
  }

  .divider {
    background: #e9e9e9;
    height: 1px;
    margin: 3.5px 8px 0;
  }

  .stat-chart > .comp-chart {
    position: relative;
  }

  .mask-bar-container {
    padding: 9px 8px;

    .mask-bar {
      background: #e9e9e9;
      height: 20px;
      margin-top: 2px;
    }
  }

  &:hover {
    .column-name {
      max-width: 60px;
    }

    .unique-value-count {
      max-width: 34px;
    }

    .btns {
      display: block;
    }
  }

  .icon-container {
    cursor: pointer;
    font-size: 12px;
    line-height: 12px;
    text-align: right;
  }

  .stat-chart {
    position: relative;
  }

  .search-box {
    display: flex;
    justify-content: center;
    padding-top: 5px;

    .search-input {
      width: 88%;
    }
  }

  .chart-options-input {
    font-size: 12px;
    height: 22px;
    left: 0;
    margin-left: 5px;
    position: absolute;
    top: 0;
    width: 165px;
    z-index: 900;
  }
}
</style>
